#include "ReferenceSequence.h"

#include <string>
#include <cerrno>
#include <cstddef>
#include "WindowReader.h"

const int ReferenceSequence::BUFFER_SIZE = 4096;

#ifdef DEBUG

 #include <iostream>
using namespace std;
#endif /* DEBUG */

ReferenceSequence::ReferenceSequence(ifstream &in, ulong maxStrLen, size_t windowSize) :
		strLen(0),
		maxPairBlock(0),
		pairs(new SeqBlock[maxStrLen/CHARS_PER_BLOCK]),
		subsHash(new MinLengthSubs())
{
	char block[BUFFER_SIZE];
	bool nameRead = false;
	WindowReader window(0);

	int i = 0;
	streamsize bytesRead = 0;
	uint curShift = 0;
	SeqBlock newPair = 0;

	/* Treat the first windowSize chars specially (just add them to the window) */
	while ((!in.eof()) && (strLen < windowSize - 1))
	{
		in.read(&block[0], BUFFER_SIZE);
		bytesRead = in.gcount();
		for (i = 0; (i < bytesRead) && (strLen < windowSize - 1); i++)
		{
			/* Just read the sequence name until the first new-line */
			if (!nameRead)
			{
				if (block[i] == '\n')
					nameRead = true;
			}
			else
			{
				/* enter the data to the bitsets */
				switch (block[i])
				{
				case 'A':
					newPair |= (CHAR_A << curShift);
					window.push(CHAR_A);
					curShift += 2;
					strLen++;
					break;
				case 'C':
					newPair |= (CHAR_C << curShift);
					window.push(CHAR_C);
					curShift += 2;
					strLen++;
					break;
				case 'G':
					newPair |= (CHAR_G << curShift);
					window.push(CHAR_G);
					curShift += 2;
					strLen++;
					break;
				case 'T':
					newPair |= (CHAR_T << curShift);
					window.push(CHAR_T);
					curShift += 2;
					strLen++;
					break;
				default:
					/* Do nothing */
					break;
				}
				if (curShift == 64)
				{
					curShift = 0;
					pairs[maxPairBlock++] = newPair;
					newPair = 0;
				}
			}
		}
	}

	/* NOTE: The repeated code is to avoid additional if condition checking, (so that other chars in the file will not be calculated) */
	/* enter the rest of the read bytes to the bitsets and the window to the hash */
	for (; i < bytesRead; i++)
	{
		/* enter the data to the bitsets */
		switch (block[i])
		{
		case 'A':
			newPair |= (CHAR_A << curShift);
			window.push(CHAR_A);
			curShift += 2;
			strLen++;
#ifdef DEBUG
			cout << "Adding window with position " << (strLen - windowSize) << ": ";
			window.printStr();
			cout << endl;
#endif /* DEBUG */
			subsHash->addSubstring(window.getContents(), strLen - windowSize);
			break;
		case 'C':
			newPair |= (CHAR_C << curShift);
			window.push(CHAR_C);
			curShift += 2;
			strLen++;
#ifdef DEBUG
			cout << "Adding window with position " << (strLen - windowSize) << ": ";
			window.printStr();
			cout << endl;
#endif /* DEBUG */
			subsHash->addSubstring(window.getContents(), strLen - windowSize);
			break;
		case 'G':
			newPair |= (CHAR_G << curShift);
			window.push(CHAR_G);
			curShift += 2;
			strLen++;
#ifdef DEBUG
			cout << "Adding window with position " << (strLen - windowSize) << ": ";
			window.printStr();
			cout << endl;
#endif /* DEBUG */
			subsHash->addSubstring(window.getContents(), strLen - windowSize);
			break;
		case 'T':
			newPair |= (CHAR_T << curShift);
			window.push(CHAR_T);
			curShift += 2;
			strLen++;
#ifdef DEBUG
			cout << "Adding window with position " << (strLen - windowSize) << ": ";
			window.printStr();
			cout << endl;
#endif /* DEBUG */
			subsHash->addSubstring(window.getContents(), strLen - windowSize);
			break;
		default:
			/* Do nothing */
			break;
		}
		if (curShift == 64)
		{
			curShift = 0;
			pairs[maxPairBlock++] = newPair;
			newPair = 0;
		}
	}


	/* The rest of the chars need to be inserted to  chars specially (just add them to the window) */
	while (!in.eof())
	{
		in.read(&block[0], BUFFER_SIZE);
		bytesRead = in.gcount();
		for (i = 0; i < bytesRead; i++)
		{
			/* enter the data to the bitsets */
			switch (block[i])
			{
			case 'A':
				newPair |= (CHAR_A << curShift);
				///bitsets[CHAR_A]->set(strLen++);
				window.push(CHAR_A);
				curShift += 2;
				strLen++;
#ifdef DEBUG
				cout << "Adding window with position " << (strLen - windowSize) << ": ";
				window.printStr();
				cout << endl;
#endif /* DEBUG */
				subsHash->addSubstring(window.getContents(), strLen - windowSize);
				break;
			case 'C':
				newPair |= (CHAR_C << curShift);
				///bitsets[CHAR_C]->set(strLen++);
				window.push(CHAR_C);
				curShift += 2;
				strLen++;
#ifdef DEBUG
				cout << "Adding window with position " << (strLen - windowSize) << ": ";
				window.printStr();
				cout << endl;
#endif /* DEBUG */
				subsHash->addSubstring(window.getContents(), strLen - windowSize);
				break;
			case 'G':
				newPair |= (CHAR_G << curShift);
				///bitsets[CHAR_G]->set(strLen++);
				window.push(CHAR_G);
				curShift += 2;
				strLen++;
#ifdef DEBUG
				cout << "Adding window with position " << (strLen - windowSize) << ": ";
				window.printStr();
				cout << endl;
#endif /* DEBUG */
				subsHash->addSubstring(window.getContents(), strLen - windowSize);
				break;
			case 'T':
				newPair |= (CHAR_T << curShift);
				///bitsets[CHAR_T]->set(strLen++);
				window.push(CHAR_T);
				curShift += 2;
				strLen++;
#ifdef DEBUG
				cout << "Adding window with position " << (strLen - windowSize) << ": ";
				window.printStr();
				cout << endl;
#endif /* DEBUG */
				subsHash->addSubstring(window.getContents(), strLen - windowSize);
				break;
			default:
				/* Do nothing */
				break;
			}
			if (curShift == 64)
			{
				curShift = 0;
				pairs[maxPairBlock++] = newPair;
				newPair = 0;
			}
		}
	}

	// Add the residue of the sequence if there is...
	if (curShift > 0)
		pairs[maxPairBlock] = newPair;


}

ReferenceSequence::~ReferenceSequence()
{
///	for (int i = 0; i < CHAR_NUM; i++)
///		delete bitsets[i];
	delete subsHash;
	delete pairs;
}

/*
int ReferenceSequence::getBit(short c, size_t indx) const
{
	return bitsets[c]->test(indx);
}
*/

inline char ReferenceSequence::getChar(const size_t index) const
{
	SeqBlock word = pairs[index >> INDEX_DIV_SHIFT]; // index / 32
	int shift = ((index & INDEX_MOD_MASK) << 1);
	ulong mask = 3UL << shift;
	switch ((word & mask) >> shift)
	{
	case 0:
		return 'A';
		break;
	case 1:
		return 'C';
		break;
	case 2:
		return 'G';
		break;
	case 3:
		return 'T';
		break;
	default:
		/* Error - should not reach here */
		return '\0';
		break;
	}
}

/* The returned reference is reversed */
inline SeqBlock ReferenceSequence::getBlock(const size_t fromIndex) const
{
	size_t pairsIndex = fromIndex >> INDEX_DIV_SHIFT; // fromIndex / 32
	SeqBlock res = pairs[pairsIndex]; // index / 32
	int shift = ((fromIndex & INDEX_MOD_MASK) << 1);
	// If we need to take some bits from the next pairs block (and that block exists)...
	if ((shift != 0) && (pairsIndex < maxPairBlock))
	{
		SeqBlock nextBlock = pairs[pairsIndex + 1];
		res >>= shift;
		nextBlock <<= (64 - shift);
		res |= nextBlock;
	}

	return res;
}

const MinLengthSubs *ReferenceSequence::getSubsHash() const
{
	return subsHash;
}

#ifdef DEBUG

 #include <iostream>
using namespace std;

void ReferenceSequence::print() const
{
	cout << "Reference sequence:" << endl;

	for (size_t i = 0; i < strLen; i++)
	{
		SeqBlock word = pairs[i / CHARS_PER_BLOCK];
		int shift = ((i % CHARS_PER_BLOCK) << 1);
		ulong mask = 3UL << shift;
		switch ((word & mask) >> shift)
		{
		case 0:
			cout << 'A';
			break;
		case 1:
			cout << 'C';
			break;
		case 2:
			cout << 'G';
			break;
		case 3:
			cout << 'T';
			break;
		default:
			/* Error - should not reach here */
			break;
		}
		if ((i + 1) % 81 == 0)
			cout << endl;
	}
	cout << endl;

	subsHash->print();
}
#endif /* DEBUG */

